/**
 *
 */
package com.bblocks.common;

import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.json.JSONUtil;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.bblocks.enums.SysEnum;
import com.bblocks.exception.ServiceException;
import com.bblocks.util.ServletUtils;
import com.pig4cloud.pig.common.core.util.WebUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @version:v1.0
 * @Description: 防止重复提交(只是基于地址的 ( 参数未涉及)，所以无关的接口尽量不要防重复提交)
 * @author: jzhao
 * @date: 2019年9月10日下午7:29:36
 */
@Aspect
@Component
@Slf4j
public class RepeatSubmitAspect {
	public static final String LOCK_REPEAT_PRE_KEY = "lock_repeat::";

	private SpelExpressionParser spelParser = new SpelExpressionParser();
	@Autowired
	RedisTemplate redisTemplate;

	@Pointcut("@annotation(noRepeatSubmit)")
	public void pointCut(NoRepeatSubmit noRepeatSubmit) {
	}

	@Around("pointCut(noRepeatSubmit)")
	public Object around(ProceedingJoinPoint joinPoint, NoRepeatSubmit noRepeatSubmit) throws Throwable {
		String key = this.finalKey(joinPoint,noRepeatSubmit);
		log.debug("lock key：{}",key);
		try {
			if (redisTemplate.hasKey(key)) {
				throw new ServiceException(SysEnum.ERR_REPEAT);
			}
			// 如果是第一次请求,就将 key 当前对象压入缓存中
			ValueOperations<String, Object> ops = redisTemplate.opsForValue();
			ops.set(key, key, noRepeatSubmit.lockTime(), TimeUnit.MILLISECONDS);
			return joinPoint.proceed();
		} catch (Throwable throwable) {
			throw throwable;
		} finally {
			// TODO 为了演示效果,这里就不调用 CACHES.invalidate(key); 代码了
			if (noRepeatSubmit.overResume())
				redisTemplate.delete(key);
		}

	}

	/**
	 * key方案：
	 * 1、有sepl，则则token!=null?token:ip  +url+sepl值
	 * 2、无sepl，则token!=null?token:ip + url + 参数
	 * @param joinPoint
	 * @param noRepeatSubmit
	 * @return
	 */
	private String finalKey(ProceedingJoinPoint joinPoint, NoRepeatSubmit noRepeatSubmit){
		HttpServletRequest request = ServletUtils.getRequest();
		String uri = request.getRequestURI();
		String token = request.getHeader(Constant.ACCESS_TOKEN_KEY);
		String ip = WebUtils.getIP();

		// 解析SpEL表达式获取结果
		String key = getSeplKey(joinPoint, noRepeatSubmit);
		if (StrUtil.isEmpty(key)) {

			key = StrUtil.isNotEmpty(token) ? token:ip +  getSimpleKey( uri,joinPoint);
		}else {
			key = StrUtil.isNotEmpty(token) ? token:ip + uri + key;
		}



		int length = StrUtil.length(key);
		if(StrUtil.length(key)>32){
			log.debug("lock key长度太长:{}，即将md5处理！",length);
			key = SecureUtil.md5(key);
		}
		return LOCK_REPEAT_PRE_KEY + key;
	}

	private String getSimpleKey(String path,ProceedingJoinPoint joinPoint) {
		String params = getParams(joinPoint);
		return  path + (StrUtil.isEmpty(params)?"":params) ;
	}

	/**
	 * 根据 keySpel 生成key
	 * @param joinPoint
	 * @param noRepeatSubmit
	 * @return
	 */
	private String getSeplKey(ProceedingJoinPoint joinPoint, NoRepeatSubmit noRepeatSubmit) {
		if (StrUtil.isEmpty(noRepeatSubmit.keySpel()))
			return null;

		MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
		Method method = methodSignature.getMethod();
		List<String> paramNameList = Arrays.asList(methodSignature.getParameterNames());
		List<Object> paramList = Arrays.asList(joinPoint.getArgs());
		methodSignature.getDeclaringType();


		//将方法的参数名和参数值一一对应的放入上下文中
		EvaluationContext ctx = new StandardEvaluationContext();
		for (int i = 0; i < paramNameList.size(); i++) {
			ctx.setVariable(paramNameList.get(i), paramList.get(i));
		}

		// 解析SpEL表达式获取结果
		String value = spelParser.parseExpression(noRepeatSubmit.keySpel()).getValue(ctx).toString();
		return methodSignature
				.getDeclaringTypeName()
				.substring(methodSignature.getDeclaringTypeName().lastIndexOf(".") + 1) + "::" + method.getName() + "::" + value;
	}

	public String getParams(ProceedingJoinPoint joinPoint){
		Signature signature = joinPoint.getSignature();
		MethodSignature methodSignature = (MethodSignature) signature;
		//2.最关键的一步:通过这获取到方法的所有参数名称的字符串数组
		String[] parameterNames = methodSignature.getParameterNames();

		Map paramMap = new HashMap();
		int i =0 ;
		for(Object arg:joinPoint.getArgs()){
			i++;
			//https://www.cnblogs.com/fswhq/p/10702857.html
			if (arg instanceof ServletRequest || arg instanceof ServletResponse || arg instanceof MultipartFile) {
				continue;
			}

			paramMap.put(parameterNames[i-1],arg);
		}
		if(!paramMap.isEmpty())
			return JSONUtil.toJsonStr(paramMap) ;
		else
			return null;
	}



}
